﻿using System;
using System.ServiceModel;

namespace BizElements.BusinessLayer
{
    /// <summary>An implementation of <see cref="IMethodRemoter"/> which remotes all method calls to a WCF service host with <see cref="IMethodReceiverService"/> service.</summary>
    public class WcfMethodSender : IMethodRemoter
    {
        /// <summary>Initializes a new instance of <b>WcfMethodSender</b> class.</summary>
        public WcfMethodSender()
        {
        }

        /// <summary>Returns <b>true</b> indicating that method calls are remoted and executed in another domain/process.</summary>
        public bool ImplementsRemoting
        {
            get { return true; }
        }

        /// <summary>Gets or sets URI string for endpoint adrress. Prefer configuring the address in <b>address</b> attribute of <b>system.serviceModel/client/endpoint</b> element in application's config file, instead of using this property.</summary>
        public string Url { get; set; }

        /// <summary>Marshals object to process defined by the remoter, executes the method and returns the changed object and method's return value to source domain.</summary>
        /// <param name="obj">Object which is to be marshalled to the defined process and supports the execution of the specified method. 
        /// May be changed in remote domain. Must be marked with <see cref="System.SerializableAttribute"/> atribute.</param>
        /// <param name="methodName">Method which is to be executed in the remote domain.</param>
        /// <param name="methodParameters">Method parameters. May not be changed in remote domain. Passed by value.
        /// All parameters must be marked with <see cref="System.SerializableAttribute"/> atribute.</param>
        /// <returns>Method's return value. Must be marked with <see cref="System.SerializableAttribute"/> atribute.</returns>
        public virtual object InvokeMethod(object obj, string methodName, params object[] methodParameters)
        {
            if (methodParameters == null)
                methodParameters = new object[0];

            byte[] serializedBusinessObject = Serializer.SerializeWithBinaryFormater(obj);
            byte[][] serializedMethodParameters = Serializer.SerializeWithBinaryFormater(methodParameters);

            // WCF InvokeMethod.
            byte[] serializedRetval = GetClient().InvokeMethod(ref serializedBusinessObject, methodName, serializedMethodParameters);
            object retval = Serializer.DeserializeWithBinaryFormater(serializedRetval);
            object modifiedBusinessObject = Serializer.DeserializeWithBinaryFormater(serializedBusinessObject);

            // Copy modified values into source object.
            ReflectionUtil.CopyObject(modifiedBusinessObject, obj);
            return retval;
        }        

        /// <summary>Marshals method arguments to process defined by the remoter, executes the method and returns the method's return value to source domain.</summary>
        /// <param name="type">Type which contains the specified static method.</param>
        /// <param name="methodName">Method to be executed.</param>
        /// <param name="methodParameters">Method parameters. May not be changed in remote domain. Passed by value.
        /// All parameters must be marked with <see cref="System.SerializableAttribute"/> atribute.</param>
        /// <returns>Method's return value. Must be marked with <see cref="System.SerializableAttribute"/> atribute.</returns>
        public virtual object InvokeStaticMethod(Type type, string methodName, params object[] methodParameters)
        {
            if (methodParameters == null)
                methodParameters = new object[0];

            byte[] serializedType = Serializer.SerializeWithBinaryFormater(type);
            byte[][] serializedMethodParameters = Serializer.SerializeWithBinaryFormater(methodParameters);
            byte[] serializedRetval = GetClient().InvokeStaticMethod(serializedType, methodName, serializedMethodParameters);
            object retval = Serializer.DeserializeWithBinaryFormater(serializedRetval);
            return retval;
        }

        /// <summary>Marshals object to process defined by the remoter, executes the generic method and returns the changed object and method's return value to source domain.</summary>
        /// <param name="obj">Object which is to be marshalled to the defined process and supports the execution of the specified method. 
        /// May be changed in remote domain. Must be marked with <see cref="System.SerializableAttribute"/> atribute.</param>
        /// <param name="methodName">Method which is to be executed in the remote domain.</param>
        /// <param name="methodParameters">Method parameters. May not be changed in remote domain. Passed by value.
        /// All parameters must be marked with <see cref="System.SerializableAttribute"/> atribute.</param>
        /// <returns>Method's return value. Must be marked with <see cref="System.SerializableAttribute"/> atribute.</returns>
        public virtual object InvokeGenericMethod(object obj, string methodName, params object[] methodParameters)
        {
            if (methodParameters == null)
                methodParameters = new object[0];

            byte[] serializedBusinessObject = Serializer.SerializeWithBinaryFormater(obj);
            byte[][] serializedMethodParameters = Serializer.SerializeWithBinaryFormater(methodParameters);

            // WCF InvokeMethod.
            byte[] serializedRetval = GetClient().InvokeGenericMethod(ref serializedBusinessObject, methodName, serializedMethodParameters);
            object retval = Serializer.DeserializeWithBinaryFormater(serializedRetval);
            object modifiedBusinessObject = Serializer.DeserializeWithBinaryFormater(serializedBusinessObject);

            // Copy modified values into source object.
            ReflectionUtil.CopyObject(modifiedBusinessObject, obj);
            return retval;
        }

        /// <summary>Marshals method arguments to process defined by the remoter, executes the generic method and returns the method's return value to source domain.</summary>
        /// <param name="type">Type which contains the specified static method.</param>
        /// <param name="methodName">Method to be executed.</param>
        /// <param name="methodParameters">Method parameters. May not be changed in remote domain. Passed by value.
        /// All parameters must be marked with <see cref="System.SerializableAttribute"/> atribute.</param>
        /// <returns>Method's return value. Must be marked with <see cref="System.SerializableAttribute"/> atribute.</returns>
        public virtual object InvokeGenericStaticMethod(Type type, string methodName, params object[] methodParameters)
        {
            if (methodParameters == null)
                methodParameters = new object[0];

            byte[] serializedType = Serializer.SerializeWithBinaryFormater(type);
            byte[][] serializedMethodParameters = Serializer.SerializeWithBinaryFormater(methodParameters);
            byte[] serializedRetval = GetClient().InvokeGenericStaticMethod(serializedType, methodName, serializedMethodParameters);
            object retval = Serializer.DeserializeWithBinaryFormater(serializedRetval);
            return retval;
        }

        private WcfMethodReceiverServiceClient GetClient()
        {
            return new WcfMethodReceiverServiceClient() { Url = this.Url };
        }
    }    
}
